/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fresco.helper.photoview.photodraweeview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.timelinecurves.SpringCurve;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.ScrollHelper;
import ohos.agp.utils.Matrix;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.multimodalinput.event.TouchEvent;

import com.alexvasilkov.gestures.GestureDetector;
import com.alexvasilkov.gestures.MatrixHelper;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.view.DraweeView;
import com.facebook.fresco.helper.utils.MLog;
import com.oszc.bbhmlibrary.wrapper.RectF;

import java.lang.ref.WeakReference;

/**
 * 连接器
 *
 * @author dev
 * @since 2021-08-02
 */
public class Attacher implements IAttacher, Component.TouchEventListener, OnScaleDragGestureListener {
    private static final int EDGE_NONE = -1;
    private static final int EDGE_LEFT = 0;
    private static final int EDGE_RIGHT = 1;
    private static final int EDGE_BOTH = 2;

    private final float[] mMatrixValues = new float[9];
    private final RectF mDisplayRect = new RectF();
    private final Animator.TimelineCurve mZoomInterpolator = new SpringCurve();

    private float mMinScale = IAttacher.DEFAULT_MIN_SCALE;
    private float mMidScale = IAttacher.DEFAULT_MID_SCALE;
    private float mMaxScale = IAttacher.DEFAULT_MAX_SCALE;
    private long mZoomDuration = IAttacher.ZOOM_DURATION;

    private ScaleDragDetector mScaleDragDetector;
    private GestureDetector mGestureDetector;

    private boolean mBlockParentIntercept = false;
    private boolean mAllowParentInterceptOnEdge = true;
    private int mScrollEdge = EDGE_BOTH;
    private MatrixHelper matrixHelper = MatrixHelper.getInstance();
    private Matrix mMatrix = matrixHelper.obtainIdentityMatrix();
    private int mImageInfoHeight = -1;
    private int mImageInfoWidth = -1;
    private FlingRunnable mCurrentFlingRunnable;
    private WeakReference<DraweeView<GenericDraweeHierarchy>> mDraweeView;

    private OnPhotoTapListener mPhotoTapListener;
    private OnViewTapListener mViewTapListener;
    private Component.LongClickedListener mLongClickListener;
    private OnScaleChangeListener mScaleChangeListener;
    private final DefaultOnDoubleTapListener defaultOnDoubleTapListener;

    /**
     * 连接器
     *
     * @param draweeView 付款人的观点
     */
    public Attacher(DraweeView<GenericDraweeHierarchy> draweeView) {
        mDraweeView = new WeakReference<>(draweeView);
        draweeView.getHierarchy().setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER);
        draweeView.setTouchEventListener(this);
        defaultOnDoubleTapListener = new DefaultOnDoubleTapListener(this);
        mScaleDragDetector = new ScaleDragDetector(draweeView.getContext(), this);
        mGestureDetector = new GestureDetector(draweeView.getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public void onLongPress(TouchEvent event) {
                super.onLongPress(event);
                if (null != mLongClickListener) {
                    mLongClickListener.onLongClicked(getDraweeView());
                }
            }
        });
        mGestureDetector.setOnDoubleTapListener(defaultOnDoubleTapListener);
    }

    public DefaultOnDoubleTapListener getDefaultOnDoubleTapListener() {
        return defaultOnDoubleTapListener;
    }

    /**
     * 双击侦听器
     *
     * @param newOnDoubleTapListener 新上双击侦听器
     */
    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
        if (newOnDoubleTapListener != null) {
            this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
        } else {
            this.mGestureDetector.setOnDoubleTapListener(defaultOnDoubleTapListener);
        }
    }

    /**
     * 获得付款人的观点
     *
     * @return {@link DraweeView}
     */
    public DraweeView<GenericDraweeHierarchy> getDraweeView() {
        return mDraweeView.get();
    }

    @Override
    public float getMinimumScale() {
        return mMinScale;
    }

    @Override
    public float getMediumScale() {
        return mMidScale;
    }

    @Override
    public float getMaximumScale() {
        return mMaxScale;
    }

    @Override
    public void setMaximumScale(float maximumScale) {
        checkZoomLevels(mMinScale, mMidScale, maximumScale);
        mMaxScale = maximumScale;
    }

    @Override
    public void setMediumScale(float mediumScale) {
        checkZoomLevels(mMinScale, mediumScale, mMaxScale);
        mMidScale = mediumScale;
    }

    @Override
    public void setMinimumScale(float minimumScale) {
        checkZoomLevels(minimumScale, mMidScale, mMaxScale);
        mMinScale = minimumScale;
    }

    @Override
    public float getScale2() {
        return (float) Math.sqrt(
            (float) Math.pow(getMatrixValue(mMatrix, (int) mMatrix.getScaleX()), 2) + (float) Math.pow(
                getMatrixValue(mMatrix, (int) mMatrix.getSkewY()), 2));
    }

    @Override
    public void setScale(float scale) {
        setScale(scale, false);
    }

    @Override
    public void setScale(float scale, boolean isAnimate) {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView != null) {
            int right = draweeView.getRight() / 2;
            int bottom = draweeView.getBottom() / 2;
            setScale(scale, right, bottom, false);
        }
    }

    @Override
    public void setScale(float scale, float focalX, float focalY, boolean animate) {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null || scale < mMinScale || scale > mMaxScale) {
            return;
        }
        if (animate) {
            TaskDispatcher uiTaskDispatcher = draweeView.getContext().getUITaskDispatcher();
            uiTaskDispatcher.asyncDispatch(new AnimatedZoomRunnable(getScale2(), scale, focalX, focalY));
        } else {
            mMatrix.setScale(scale, scale, focalX, focalY);
            checkMatrixAndInvalidate();
        }
    }

    @Override
    public void setZoomTransitionDuration(long duration) {
        long duration2;
        duration2 = duration < 0 ? IAttacher.ZOOM_DURATION : duration;
        mZoomDuration = duration2;
    }

    @Override
    public void setAllowParentInterceptOnEdge(boolean isAllow) {
        mAllowParentInterceptOnEdge = isAllow;
    }


    @Override
    public void setOnScaleChangeListener(OnScaleChangeListener listener) {
        mScaleChangeListener = listener;
    }

    @Override
    public void setOnLongClickListener(Component.LongClickedListener listener) {
        mLongClickListener = listener;
    }

    @Override
    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
        mPhotoTapListener = listener;
    }

    @Override
    public void setOnViewTapListener(OnViewTapListener listener) {
        mViewTapListener = listener;
    }

    @Override
    public OnPhotoTapListener getOnPhotoTapListener() {
        return mPhotoTapListener;
    }

    @Override
    public OnViewTapListener getOnViewTapListener() {
        return mViewTapListener;
    }

    @Override
    public void update(int imageInfoWidth, int imageInfoHeight) {
        mImageInfoWidth = imageInfoWidth;
        mImageInfoHeight = imageInfoHeight;
        updateBaseMatrix();
    }

    private static void checkZoomLevels(float minZoom, float midZoom, float maxZoom) {
        if (minZoom >= midZoom) {
            MLog.error("MinZoom has to be less than MidZoom");
        } else if (midZoom >= maxZoom) {
            MLog.error("MidZoom has to be less than MaxZoom");
        }
    }

    private int getViewWidth() {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView != null) {
            return draweeView.getWidth()
                - draweeView.getPaddingLeft()
                - draweeView.getPaddingRight();
        }
        return 0;
    }

    private int getViewHeight() {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView != null) {
            return draweeView.getHeight()
                - draweeView.getPaddingTop()
                - draweeView.getPaddingBottom();
        }
        return 0;
    }

    private float getMatrixValue(Matrix matrix, int whichValue) {
        matrixHelper.getValues(matrix, mMatrixValues);
        return mMatrixValues[whichValue];
    }

    /**
     * 得到画矩阵
     *
     * @return {@link Matrix}
     */
    public Matrix getDrawMatrix() {
        return mMatrix;
    }

    /**
     * 得到显示矩形
     *
     * @return {@link RectF}
     */
    public RectF getDisplayRect() {
        checkMatrixBounds();
        return getDisplayRect(getDrawMatrix());
    }

    private RectF getDisplayRect(Matrix matrix) {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null || (mImageInfoWidth == -1 && mImageInfoHeight == -1)) {
            return null;
        }
        mDisplayRect.set(0.0F, 0.0F, mImageInfoWidth, mImageInfoHeight);
        draweeView.getHierarchy().getActualImageBounds(mDisplayRect);
        matrix.mapRect(mDisplayRect);
        return mDisplayRect;
    }

    /**
     * 校验矩阵和无效
     */
    public void checkMatrixAndInvalidate() {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null) {
            return;
        }
        if (checkMatrixBounds()) {
            draweeView.invalidate();
        }
    }

    /**
     * 校验矩阵的界限
     *
     * @return boolean
     */
    public boolean checkMatrixBounds() {
        RectF rect = getDisplayRect(getDrawMatrix());
        if (rect == null) {
            return false;
        }

        float height = rect.getHeight();
        float width = rect.getWidth();
        float deltaX = 0.0F;
        float deltaY = 0.0F;
        int viewHeight = getViewHeight();

        if (height <= (float) viewHeight) {
            deltaY = (viewHeight - height) / 2 - rect.top;
        } else if (rect.top > 0.0F) {
            deltaY = -rect.top;
        } else if (rect.bottom < (float) viewHeight) {
            deltaY = viewHeight - rect.bottom;
        }
        int viewWidth = getViewWidth();
        if (width <= viewWidth) {
            deltaX = (viewWidth - width) / 2 - rect.left;
            mScrollEdge = EDGE_BOTH;
        } else if (rect.left > 0) {
            deltaX = -rect.left;
            mScrollEdge = EDGE_LEFT;
        } else if (rect.right < viewWidth) {
            deltaX = viewWidth - rect.right;
            mScrollEdge = EDGE_RIGHT;
        } else {
            mScrollEdge = EDGE_NONE;
        }

        mMatrix.postTranslate(deltaX, deltaY);
        return true;
    }

    private void updateBaseMatrix() {
        if (mImageInfoWidth == -1 && mImageInfoHeight == -1) {
            return;
        }
        resetMatrix();
    }

    private void resetMatrix() {
        mMatrix.reset();
        checkMatrixBounds();
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView != null) {
            draweeView.invalidate();
        }
    }

    private void checkMinScale() {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null) {
            return;
        }
        if (getScale2() < mMinScale) {
            RectF rect = getDisplayRect();
            if (null != rect) {
                TaskDispatcher uiTaskDispatcher = draweeView.getContext().getUITaskDispatcher();
                uiTaskDispatcher.asyncDispatch(new AnimatedZoomRunnable(getScale2(),
                    mMinScale, rect.getCenter().getPointX(), rect.getCenter().getPointY()));
            }
        }
    }

    @Override
    public void onScale(float scaleFactor, float focusX, float focusY) {
        if (getScale2() < mMaxScale || scaleFactor < 1.0F) {

            if (mScaleChangeListener != null) {
                mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
            }

            mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
            checkMatrixAndInvalidate();
        }
    }

    @Override
    public void onScaleEnd() {
        checkMinScale();
    }

    @Override
    public void onDrag(float dx, float dy) {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView != null && !mScaleDragDetector.isScaling()) {
            mMatrix.postTranslate(dx, dy);
            checkMatrixAndInvalidate();
            ComponentParent parent = draweeView.getComponentParent();
            if (parent == null) {
                return;
            }
            if (mAllowParentInterceptOnEdge
                && !mScaleDragDetector.isScaling()
                && !mBlockParentIntercept) {
                if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f) || (
                    mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
//                    parent.requestDisallowInterceptTouchEvent(false);
                }
            } else {
//                parent.requestDisallowInterceptTouchEvent(true);
            }
        }
    }

    @Override
    public void onFling(float startX, float startY, float velocityX, float velocityY) {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null) {
            return;
        }
        mCurrentFlingRunnable = new FlingRunnable(draweeView.getContext());
        mCurrentFlingRunnable.fling(getViewWidth(), getViewHeight(), (int) velocityX,
            (int) velocityY);
        TaskDispatcher uiTaskDispatcher = draweeView.getContext().getUITaskDispatcher();
        uiTaskDispatcher.asyncDispatch(mCurrentFlingRunnable);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        int action = touchEvent.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                ComponentParent componentParent = component.getComponentParent();
                if (componentParent != null) {
//                    componentParent.requestDisallowInterceptTouchEvent(true);
                }
                cancelFling();
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                ComponentParent parent = component.getComponentParent();
                if (parent != null) {
//                    parent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            default:
                break;
        }
        boolean wasScaling = mScaleDragDetector.isScaling();
        boolean wasDragging = mScaleDragDetector.isDragging();
        boolean handled = mScaleDragDetector.onTouchEvent(touchEvent);

        boolean noScale = !wasScaling && !mScaleDragDetector.isScaling();
        boolean noDrag = !wasDragging && !mScaleDragDetector.isDragging();
        mBlockParentIntercept = noScale && noDrag;

        if (mGestureDetector.onTouchEvent(touchEvent)) {
            handled = true;
        }
        return handled;
    }

    /**
     * 动画缩放可运行
     *
     * @author dev
     * @since 2021-08-03
     */
    private class AnimatedZoomRunnable implements Runnable {
        private float mFocalX;
        private float mFocalY;
        private long mStartTime;
        private float mZoomStart;
        private float mZoomEnd;

        /**
         * 动画缩放可运行
         *
         * @param currentZoom 电流放大
         * @param targetZoom 目标放大
         * @param focalX focalx
         * @param focalY 恰城对妇女实施
         */
        AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
                             float focalX, final float focalY) {
            mFocalX = focalX;
            mFocalY = focalY;
            mStartTime = System.currentTimeMillis();
            mZoomStart = currentZoom;
            mZoomEnd = targetZoom;
        }

        @Override
        public void run() {
            DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
            if (draweeView == null) {
                return;
            }

            float t = interpolate();
            float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
            float deltaScale = scale / getScale2();

            onScale(deltaScale, mFocalX, mFocalY);

            if (t < 1f) {
                postOnAnimation(draweeView, this);
            }
        }

        private float interpolate() {
            float tt = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
            tt = Math.min(1f, tt);
            tt = mZoomInterpolator.getCurvedTime(tt);
            return tt;
        }
    }

    /**
     * 投入运行的
     *
     * @author dev
     * @since 2021-08-03
     */
    private class FlingRunnable implements Runnable {
        private ScrollHelper mScroller;
        private int mCurrentX;
        private int mCurrentY;

        /**
         * 构造参数
         *
         * @param context 上下文
         */
        FlingRunnable(Context context) {
            this.mScroller = new ScrollHelper();
        }

        /**
         * 取消扔
         */
        public void cancelFling() {
            mScroller.abortAnimation();
        }

        /**
         * 手势抬起
         *
         * @param viewWidth 视图的宽度
         * @param viewHeight 视图的高度
         * @param velocityX velocityx
         * @param velocityY velocityy
         */
        public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
            RectF rect = getDisplayRect();
            if (null == rect) {
                return;
            }
            int minX;
            int maxX;
            int minY;
            int maxY;
            int startX = Math.round(-rect.left);
            if (viewWidth < rect.getWidth()) {
                minX = 0;
                maxX = Math.round(rect.getWidth() - viewWidth);
            } else {
                maxX = startX;
                minX = startX;
            }

            int startY = Math.round(-rect.top);
            if (viewHeight < rect.getHeight()) {
                minY = 0;
                maxY = Math.round(rect.getHeight() - viewHeight);
            } else {
                minY = startY;
                maxY = startY;
            }
            mCurrentX = startX;
            mCurrentY = startY;

            if (startX != maxX || startY != maxY) {
                mScroller.doFling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
            }
        }

        @Override
        public void run() {
            if (mScroller.isFinished()) {
                return;
            }
            DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
            if (draweeView != null && mScroller.isFinished()) {
                final int newX = mScroller.getScrollDistanceX();
                final int newY = mScroller.getScrollDistanceY();
                mMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
                draweeView.invalidate();
                mCurrentX = newX;
                mCurrentY = newY;
                postOnAnimation(draweeView, this);
            }
        }

    }

    private void cancelFling() {
        if (mCurrentFlingRunnable != null) {
            mCurrentFlingRunnable.cancelFling();
            mCurrentFlingRunnable = null;
        }
    }

    private void postOnAnimation(Component view, Runnable runnable) {
        Context context = view.getContext();
        TaskDispatcher uiTaskDispatcher = context.getUITaskDispatcher();
        uiTaskDispatcher.asyncDispatch(runnable);
    }

    /**
     * 远离窗户
     */
    public void onDetachedFromWindow() {
        cancelFling();
    }
}
